package cn.iocoder.yudao.module.pay.framework.impl.weixin;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.date.TemporalAccessorUtil;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.util.io.FileUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
import cn.iocoder.yudao.module.api.pay.order.dto.UnifiedOrderCreateDTO;
import cn.iocoder.yudao.module.api.pay.order.eums.UnifiedOrderStatusEnum;
import cn.iocoder.yudao.module.api.pay.order.vo.UnifiedOrderVO;
import cn.iocoder.yudao.module.api.pay.refund.dto.UnifiedRefundCreateDTO;
import cn.iocoder.yudao.module.api.pay.refund.vo.UnifiedRefundVO;
import cn.iocoder.yudao.module.api.pay.transfer.dto.UnifiedTransferCreateDTO;
import cn.iocoder.yudao.module.api.pay.transfer.vo.UnifiedTransferVO;
import cn.iocoder.yudao.module.pay.framework.impl.AbstractPayClient;
import com.github.binarywang.wxpay.bean.notify.*;
import com.github.binarywang.wxpay.bean.request.*;
import com.github.binarywang.wxpay.bean.result.*;
import com.github.binarywang.wxpay.bean.transfer.TransferBillsGetResult;
import com.github.binarywang.wxpay.bean.transfer.TransferBillsNotifyResult;
import com.github.binarywang.wxpay.bean.transfer.TransferBillsRequest;
import com.github.binarywang.wxpay.bean.transfer.TransferBillsResult;
import com.github.binarywang.wxpay.config.WxPayConfig;
import com.github.binarywang.wxpay.exception.WxPayException;
import com.github.binarywang.wxpay.service.WxPayService;
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.Map;
import java.util.Objects;

import static cn.hutool.core.date.DatePattern.*;

/**
 * 微信支付抽象类，实现微信统一的接口、以及部分实现（退款）
 *
 * @author 芋道源码
 */
@Slf4j
public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientConfig> {
    protected WxPayService wxPayService;

    protected AbstractWxPayClient(String channelCode, WxPayClientConfig config) {
        super(channelCode, config);
    }

    /**
     * 初始化 client 客户端
     *
     * @param tradeType 交易类型
     */
    @SneakyThrows
    protected void doInit(String tradeType) {
        // 创建 config 配置
        WxPayConfig payConfig = new WxPayConfig();
        payConfig.setTradeType(tradeType);
        payConfig.setAppId(channelConfig.getAppId());
        payConfig.setMchId(channelConfig.getMchId());
        if (Objects.equals(channelConfig.getApiVersion(), WxPayClientConfig.API_VERSION_V2)) {
            payConfig.setMchKey(channelConfig.getMchKey());
            payConfig.setKeyString(channelConfig.getKeyContent());
            payConfig.setKeyPath(FileUtils.createTempFile(channelConfig.getKeyContent()).getPath());
        } else if (Objects.equals(channelConfig.getApiVersion(), WxPayClientConfig.API_VERSION_V3)) {
            // 参阅 WxPayConfig#initApiV3HttpClient 源码，XXXPath 是必填项，不填的话将无法初始化 Verifier
            payConfig.setApiV3Key(channelConfig.getApiV3Key());
            payConfig.setPrivateKeyString(channelConfig.getPrivateKeyContent());
            payConfig.setPrivateKeyPath(FileUtils.createTempFile(channelConfig.getPrivateKeyContent()).getPath());
            payConfig.setPrivateCertString(channelConfig.getPrivateCertContent());
            payConfig.setPrivateCertPath(FileUtils.createTempFile(channelConfig.getPrivateCertContent()).getPath());
            boolean strictlyNeedWechatPaySerial = false;
            if (StrUtil.isNotBlank(channelConfig.getPublicKeyContent())) {
                payConfig.setPublicKeyId(channelConfig.getPublicKeyId());
                payConfig.setPublicKeyString(channelConfig.getPublicKeyContent());
                payConfig.setPublicKeyPath(FileUtils.createTempFile(channelConfig.getPublicKeyContent()).getPath());
                strictlyNeedWechatPaySerial = true;
            }
            payConfig.setStrictlyNeedWechatPaySerial(strictlyNeedWechatPaySerial);
        }
        log.trace("[doInit] WxPayConfig {}", payConfig);
        // 创建微信支付服务
        wxPayService = new WxPayServiceImpl();
        wxPayService.setConfig(payConfig);
    }

    // ============ 支付相关 ==========

    @Override
    protected UnifiedOrderVO doUnifiedOrder(UnifiedOrderCreateDTO dto) throws Exception {
        try {
            return switch (channelConfig.getApiVersion()) {
                case WxPayClientConfig.API_VERSION_V2 -> doUnifiedOrderV2(dto);
                case WxPayClientConfig.API_VERSION_V3 -> {
                    fixV3HttpClientConnectionPoolShutDown();
                    yield doUnifiedOrderV3(dto);
                }
                default ->
                        throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", channelConfig.getApiVersion()));
            };
        } catch (WxPayException e) {
            log.error("[doUnifiedOrder][支付({}) 发起微信支付异常", dto, e);
            String errorCode = getErrorCode(e);
            String errorMessage = getErrorMessage(e);
            return UnifiedOrderVO.closedOf(errorCode, errorMessage, dto.getTradeNo(), e.getXmlString());
        }
    }

    /**
     * 【V2】调用支付渠道，统一下单
     *
     * @param dto 下单信息
     * @return 各支付渠道的返回结果
     */
    protected abstract UnifiedOrderVO doUnifiedOrderV2(UnifiedOrderCreateDTO dto)
            throws Exception;

    /**
     * 【V3】调用支付渠道，统一下单
     *
     * @param dto 下单信息
     * @return 各支付渠道的返回结果
     */
    protected abstract UnifiedOrderVO doUnifiedOrderV3(UnifiedOrderCreateDTO dto)
            throws WxPayException;

    /**
     * 【V2】创建微信下单请求
     *
     * @param dto 下信息
     * @return 下单请求
     */
    protected WxPayUnifiedOrderRequest buildPayUnifiedOrderRequestV2(UnifiedOrderCreateDTO dto) {
        return WxPayUnifiedOrderRequest.newBuilder()
                .outTradeNo(dto.getPayNo())
                .body(dto.getSubject())
                .detail(dto.getBody())
                .totalFee(dto.getPrice()) // 单位分
                .timeExpire(formatDateV2(dto.getExpireTime()))
                .spbillCreateIp(dto.getUserIp())
                .notifyUrl(dto.getNotifyUrl())
                .build();
    }

    /**
     * 【V3】创建微信下单请求
     *
     * @param dto 下信息
     * @return 下单请求
     */
    protected WxPayUnifiedOrderV3Request buildPayUnifiedOrderRequestV3(UnifiedOrderCreateDTO dto) {
        WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
        request.setOutTradeNo(dto.getPayNo());
        request.setDescription(dto.getSubject());
        request.setAmount(new WxPayUnifiedOrderV3Request.Amount().setTotal(dto.getPrice())); // 单位分
        request.setTimeExpire(formatDateV3(dto.getExpireTime()));
        request.setSceneInfo(new WxPayUnifiedOrderV3Request.SceneInfo().setPayerClientIp(dto.getUserIp()));
        request.setNotifyUrl(dto.getNotifyUrl());
        return request;
    }

    @Override
    public UnifiedOrderVO doParseOrderNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
        return switch (channelConfig.getApiVersion()) {
            case WxPayClientConfig.API_VERSION_V2 -> doParseOrderNotifyV2(body);
            case WxPayClientConfig.API_VERSION_V3 -> doParseOrderNotifyV3(body, headers);
            default ->
                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", channelConfig.getApiVersion()));
        };
    }

    private UnifiedOrderVO doParseOrderNotifyV2(String body) throws WxPayException {
        // 1. 解析回调
        WxPayOrderNotifyResult response = wxPayService.parseOrderNotifyResult(body);
        // 2. 构建结果
        // V2 微信支付的回调，只有 SUCCESS 支付成功、CLOSED 支付失败两种情况，无需像支付宝一样解析的比较复杂
        Integer status = Objects.equals(response.getResultCode(), "SUCCESS") ?
                UnifiedOrderStatusEnum.SUCCESS.getStatus() : UnifiedOrderStatusEnum.CLOSED.getStatus();
        return UnifiedOrderVO.ofNotify(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), response.getOutTradeNo(), response.getResultCode(), response.getReturnMsg(), body);
    }

    private UnifiedOrderVO doParseOrderNotifyV3(String body, Map<String, String> headers) throws WxPayException {
        // 1. 解析回调
        SignatureHeader signatureHeader = getRequestHeader(headers);
        WxPayNotifyV3Result response = wxPayService.parseOrderNotifyV3Result(body, signatureHeader);
        WxPayNotifyV3Result.DecryptNotifyResult result = response.getResult();
        // 2. 构建结果
        Integer status = parseStatus(result.getTradeState());
        String openid = result.getPayer() != null ? result.getPayer().getOpenid() : null;
        return UnifiedOrderVO.ofNotify(status, result.getTransactionId(), openid, parseDateV3(result.getSuccessTime()),
                result.getOutTradeNo(), result.getTradeState(), result.getTradeStateDesc(), body);
    }

    @Override
    protected UnifiedOrderVO doGetOrder(String payNo) throws Throwable {
        try {
            return switch (channelConfig.getApiVersion()) {
                case WxPayClientConfig.API_VERSION_V2 -> doGetOrderV2(payNo);
                case WxPayClientConfig.API_VERSION_V3 -> doGetOrderV3(payNo);
                default ->
                        throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", channelConfig.getApiVersion()));
            };
        } catch (WxPayException e) {
            if (ObjectUtils.equalsAny(e.getErrCode(), "ORDERNOTEXIST", "ORDER_NOT_EXIST")) {
                String errorCode = getErrorCode(e);
                String errorMessage = getErrorMessage(e);
                return UnifiedOrderVO.closedOf(errorCode, errorMessage, payNo, e.getXmlString());
            }
            throw e;
        }
    }

    private UnifiedOrderVO doGetOrderV2(String payNo) throws WxPayException {
        WxPayOrderQueryRequest request = WxPayOrderQueryRequest.newBuilder()
                .outTradeNo(payNo)
                .build();
        WxPayOrderQueryResult response = wxPayService.queryOrder(request);
        Integer status = parseStatus(response.getTradeState());
        return UnifiedOrderVO.ofNotify(status, response.getTransactionId(), response.getOpenid(), parseDateV2(response.getTimeEnd()), payNo, response.getTradeState(), response.getTradeStateDesc(), response);
    }

    private UnifiedOrderVO doGetOrderV3(String payNo) throws WxPayException {
        fixV3HttpClientConnectionPoolShutDown();
        WxPayOrderQueryV3Request request = new WxPayOrderQueryV3Request()
                .setOutTradeNo(payNo);
        WxPayOrderQueryV3Result response = wxPayService.queryOrderV3(request);
        Integer status = parseStatus(response.getTradeState());
        String openid = response.getPayer() != null ? response.getPayer().getOpenid() : null;
        return UnifiedOrderVO.ofNotify(status, response.getTransactionId(), openid, parseDateV3(response.getSuccessTime()),
                payNo, response.getTradeState(), response.getTradeStateDesc(), response);
    }

    private static Integer parseStatus(String tradeState) {
        return switch (tradeState) {
            case "NOTPAY", "USERPAYING" -> // 支付中，等待用户输入密码（条码支付独有）
                    UnifiedOrderStatusEnum.WAITING.getStatus();
            case "SUCCESS" -> UnifiedOrderStatusEnum.SUCCESS.getStatus();
            case "REFUND" -> UnifiedOrderStatusEnum.REFUND.getStatus(); // 已撤销（刷卡支付独有）
            case "CLOSED", "REVOKED", "PAYERROR" -> UnifiedOrderStatusEnum.CLOSED.getStatus(); // 支付失败（其它原因，如银行返回失败）
            default -> throw new IllegalArgumentException(StrUtil.format("未知的支付状态({})", tradeState));
        };
    }

    // ============ 退款相关 ==========

    @Override
    protected UnifiedRefundVO doUnifiedRefund(UnifiedRefundCreateDTO dto) throws Throwable {
        try {
            return switch (channelConfig.getApiVersion()) {
                case WxPayClientConfig.API_VERSION_V2 -> doUnifiedRefundV2(dto);
                case WxPayClientConfig.API_VERSION_V3 -> doUnifiedRefundV3(dto);
                default ->
                        throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", channelConfig.getApiVersion()));
            };
        } catch (WxPayException e) {
            String errorCode = getErrorCode(e);
            String errorMessage = getErrorMessage(e);
            return UnifiedRefundVO.failureOf(errorCode, errorMessage,
                    dto.getRefundNo(), e.getXmlString());
        }
    }

    private UnifiedRefundVO doUnifiedRefundV2(UnifiedRefundCreateDTO dto) throws Throwable {
        // 1. 构建 WxPayRefundRequest 请求
        WxPayRefundRequest request = new WxPayRefundRequest()
                .setOutTradeNo(dto.getPayNo())
                .setOutRefundNo(dto.getRefundNo())
                .setRefundFee(dto.getRefundPrice())
                .setRefundDesc(dto.getReason())
                .setTotalFee(dto.getPayPrice())
                .setNotifyUrl(dto.getNotifyUrl());
        // 2.1 执行请求
        WxPayRefundResult response = wxPayService.refundV2(request);
        // 2.2 创建返回结果
        if (Objects.equals("SUCCESS", response.getResultCode())) { // V2 情况下，不直接返回退款成功，而是等待异步通知
            return UnifiedRefundVO.waitingOf(response.getRefundId(),
                    dto.getRefundNo(), response);
        }
        return UnifiedRefundVO.failureOf(dto.getRefundNo(), response);
    }

    private UnifiedRefundVO doUnifiedRefundV3(UnifiedRefundCreateDTO dto) throws Throwable {
        fixV3HttpClientConnectionPoolShutDown();
        // 1. 构建 WxPayRefundRequest 请求
        WxPayRefundV3Request request = new WxPayRefundV3Request()
                .setOutTradeNo(dto.getPayNo())
                .setOutRefundNo(dto.getRefundNo())
                .setAmount(new WxPayRefundV3Request.Amount().setRefund(dto.getRefundPrice())
                        .setTotal(dto.getPayPrice()).setCurrency("CNY"))
                .setReason(dto.getReason())
                .setNotifyUrl(dto.getNotifyUrl());
        // 2.1 执行请求
        WxPayRefundV3Result response = wxPayService.refundV3(request);
        // 2.2 创建返回结果
        if (Objects.equals("SUCCESS", response.getStatus())) {
            return UnifiedRefundVO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()),
                    dto.getRefundNo(), response);
        }
        if (Objects.equals("PROCESSING", response.getStatus())) {
            return UnifiedRefundVO.waitingOf(response.getRefundId(),
                    dto.getRefundNo(), response);
        }
        return UnifiedRefundVO.failureOf(dto.getRefundNo(), response);
    }

    @Override
    public UnifiedRefundVO doParseRefundNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
        return switch (channelConfig.getApiVersion()) {
            case WxPayClientConfig.API_VERSION_V2 -> doParseRefundNotifyV2(body);
            case WxPayClientConfig.API_VERSION_V3 -> parseRefundNotifyV3(body, headers);
            default ->
                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", channelConfig.getApiVersion()));
        };
    }

    private UnifiedRefundVO doParseRefundNotifyV2(String body) throws WxPayException {
        // 1. 解析回调
        WxPayRefundNotifyResult response = wxPayService.parseRefundNotifyResult(body);
        WxPayRefundNotifyResult.ReqInfo result = response.getReqInfo();
        // 2. 构建结果
        if (Objects.equals("SUCCESS", result.getRefundStatus())) {
            return UnifiedRefundVO.successOf(result.getRefundId(), parseDateV2B(result.getSuccessTime()),
                    result.getOutRefundNo(), response);
        }
        return UnifiedRefundVO.failureOf(result.getOutRefundNo(), response);
    }

    private UnifiedRefundVO parseRefundNotifyV3(String body, Map<String, String> headers) throws WxPayException {
        // 1. 解析回调
        SignatureHeader signatureHeader = getRequestHeader(headers);
        WxPayRefundNotifyV3Result response = wxPayService.parseRefundNotifyV3Result(body, signatureHeader);
        WxPayRefundNotifyV3Result.DecryptNotifyResult result = response.getResult();
        // 2. 构建结果
        if (Objects.equals("SUCCESS", result.getRefundStatus())) {
            return UnifiedRefundVO.successOf(result.getRefundId(), parseDateV3(result.getSuccessTime()),
                    result.getOutRefundNo(), response);
        }
        return UnifiedRefundVO.failureOf(result.getOutRefundNo(), response);
    }

    @Override
    protected UnifiedRefundVO doGetRefund(String payNo, String refundNo) throws WxPayException {
        try {
            return switch (channelConfig.getApiVersion()) {
                case WxPayClientConfig.API_VERSION_V2 -> doGetRefundV2(payNo, refundNo);
                case WxPayClientConfig.API_VERSION_V3 -> doGetRefundV3(payNo, refundNo);
                default ->
                        throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", channelConfig.getApiVersion()));
            };
        } catch (WxPayException e) {
            if (ObjectUtils.equalsAny(e.getErrCode(), "REFUNDNOTEXIST", "RESOURCE_NOT_EXISTS")) {
                String errorCode = getErrorCode(e);
                String errorMessage = getErrorMessage(e);
                return UnifiedRefundVO.failureOf(errorCode, errorMessage,
                        refundNo, e.getXmlString());
            }
            throw e;
        }
    }

    private UnifiedRefundVO doGetRefundV2(String payNo, String refundNo) throws WxPayException {
        // 1. 构建 WxPayRefundRequest 请求
        WxPayRefundQueryRequest request = WxPayRefundQueryRequest.newBuilder()
                .outTradeNo(payNo)
                .outRefundNo(refundNo)
                .build();
        // 2.1 执行请求
        WxPayRefundQueryResult response = wxPayService.refundQuery(request);
        // 2.2 创建返回结果
        if (!Objects.equals("SUCCESS", response.getResultCode())) {
            return UnifiedRefundVO.waitingOf(null,
                    refundNo, response);
        }
        WxPayRefundQueryResult.RefundRecord refund = CollUtil.findOne(response.getRefundRecords(),
                record -> record.getOutRefundNo().equals(refundNo));
        if (refund == null) {
            return UnifiedRefundVO.failureOf(refundNo, response);
        }
        return switch (refund.getRefundStatus()) {
            case "SUCCESS" ->
                    UnifiedRefundVO.successOf(refund.getRefundId(), parseDateV2B(refund.getRefundSuccessTime()), refundNo, response);
            case "PROCESSING" ->
                    UnifiedRefundVO.waitingOf(refund.getRefundId(), refundNo, response); // 退款到银行发现用户的卡作废或者冻结了，导致原路退款银行卡失败，资金回流到商户的现金帐号，需要商户人工干预，通过线下或者财付通转账的方式进行退款
            case "CHANGE", "FAIL" -> UnifiedRefundVO.failureOf(refundNo, response);
            default ->
                    throw new IllegalArgumentException(String.format("未知的退款状态(%s)", refund.getRefundStatus()));
        };
    }

    private UnifiedRefundVO doGetRefundV3(String payNo, String refundNo) throws WxPayException {
        fixV3HttpClientConnectionPoolShutDown();
        // 1. 构建 WxPayRefundRequest 请求
        WxPayRefundQueryV3Request request = new WxPayRefundQueryV3Request();
        request.setOutRefundNo(refundNo);
        // 2.1 执行请求
        WxPayRefundQueryV3Result response = wxPayService.refundQueryV3(request);
        // 2.2 创建返回结果
        return switch (response.getStatus()) {
            case "SUCCESS" ->
                    UnifiedRefundVO.successOf(response.getRefundId(), parseDateV3(response.getSuccessTime()), refundNo, response);
            case "PROCESSING" -> UnifiedRefundVO.waitingOf(response.getRefundId(), refundNo, response); // 退款异常
            case "ABNORMAL", "CLOSED" -> UnifiedRefundVO.failureOf(refundNo, response);
            default -> throw new IllegalArgumentException(String.format("未知的退款状态(%s)", response.getStatus()));
        };
    }

    @Override
    protected UnifiedTransferVO doUnifiedTransfer(UnifiedTransferCreateDTO dto) throws WxPayException {
        fixV3HttpClientConnectionPoolShutDown();
        // 1. 构建 TransferBillsRequest 请求
        TransferBillsRequest request = TransferBillsRequest.newBuilder()
                .appid(this.channelConfig.getAppId())
                .outBillNo(dto.getTransferNo())
                .transferAmount(dto.getPrice())
                .transferRemark(dto.getSubject())
                .transferSceneId(dto.getChannelExtras().get("sceneId"))
                .openid(dto.getUserAccount())
                .userName(dto.getUserName())
                .transferSceneReportInfos(JsonUtils.parseArray(dto.getChannelExtras().get("sceneReportInfos"),
                        TransferBillsRequest.TransferSceneReportInfo.class))
                .notifyUrl(dto.getNotifyUrl())
                .build();
        // 特殊：微信转账，必须 0.3 元起，才允许传入姓名
        if (dto.getPrice() < 30) {
            request.setUserName(null);
        }
        // 2.1 执行请求
        try {
            TransferBillsResult response = wxPayService.getTransferService().transferBills(request);
            System.out.println(response);

            // 2.2 创建返回结果
            String state = response.getState();
            if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
                return UnifiedTransferVO.dealingOf(response.getTransferBillNo(), response.getOutBillNo(), response)
                        .setChannelPackageInfo(response.getPackageInfo()); // 一般情况下，只有 WAIT_USER_CONFIRM 会有！
            }
            if (Objects.equals("SUCCESS", state)) {
                return UnifiedTransferVO.successOf(response.getTransferBillNo(), parseDateV3(response.getCreateTime()),
                        response.getOutBillNo(), response);
            }
            return UnifiedTransferVO.closedOf(state, response.getFailReason(),
                    response.getOutBillNo(), response);
        } catch (WxPayException e) {
            log.error("[doUnifiedTransfer][转账({}) 发起微信支付异常", dto, e);
            String errorCode = getErrorCode(e);
            String errorMessage = getErrorMessage(e);
            return UnifiedTransferVO.closedOf(errorCode, errorMessage,
                    dto.getTransferNo(), e.getXmlString());
        }
    }

    @Override
    protected UnifiedTransferVO doGetTransfer(String transferNo) throws WxPayException {
        fixV3HttpClientConnectionPoolShutDown();
        // 1. 执行请求
        TransferBillsGetResult response = wxPayService.getTransferService().getBillsByOutBillNo(transferNo);
        // 2. 创建返回结果
        String state = response.getState();
        if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
            return UnifiedTransferVO.dealingOf(response.getTransferBillNo(), transferNo, response);
        }
        if (Objects.equals("SUCCESS", state)) {
            return UnifiedTransferVO.successOf(response.getTransferBillNo(), parseDateV3(response.getUpdateTime()), transferNo, response);
        }
        return UnifiedTransferVO.closedOf(state, response.getFailReason(), transferNo, response);
    }

    @Override
    public UnifiedTransferVO doParseTransferNotify(Map<String, String> params, String body, Map<String, String> headers) throws WxPayException {
        return switch (channelConfig.getApiVersion()) {
            case WxPayClientConfig.API_VERSION_V3 -> parseTransferNotifyV3(body, headers);
            case WxPayClientConfig.API_VERSION_V2 ->
                    throw new UnsupportedOperationException("V2 版本暂不支持，建议使用 V3 版本");
            default ->
                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", channelConfig.getApiVersion()));
        };
    }

    private UnifiedTransferVO parseTransferNotifyV3(String body, Map<String, String> headers) throws WxPayException {
        // 1. 解析回调
        SignatureHeader signatureHeader = getRequestHeader(headers);
        TransferBillsNotifyResult response = wxPayService.getTransferService().parseTransferBillsNotifyResult(body, signatureHeader);
        TransferBillsNotifyResult.DecryptNotifyResult result = response.getResult();

        // 2. 创建返回结果
        String state = result.getState();
        if (ObjectUtils.equalsAny(state, "ACCEPTED", "PROCESSING", "WAIT_USER_CONFIRM", "TRANSFERING")) {
            return UnifiedTransferVO.dealingOf(result.getTransferBillNo(), result.getOutBillNo(), response);
        }
        if (Objects.equals("SUCCESS", state)) {
            return UnifiedTransferVO.successOf(result.getTransferBillNo(), parseDateV3(result.getUpdateTime()),
                    result.getOutBillNo(), response);
        }
        return UnifiedTransferVO.closedOf(state, result.getFailReason(),
                result.getOutBillNo(), response);
    }

    // ========== 各种工具方法 ==========

    /**
     * 组装请求头重的签名信息
     *
     * @see <a href="https://github.com/binarywang/weixin-java-pay-demo/blob/master/src/main/java/com/github/binarywang/demo/wx/pay/controller/WxPayV3Controller.java#L202-L221">官方示例</a>
     */
    private SignatureHeader getRequestHeader(Map<String, String> headers) {
        return SignatureHeader.builder()
                .signature(headers.get("wechatpay-signature"))
                .nonce(headers.get("wechatpay-nonce"))
                .serial(headers.get("wechatpay-serial"))
                .timeStamp(headers.get("wechatpay-timestamp"))
                .build();
    }

    // TODO @芋艿：可能是 wxjava 的 bug：https://github.com/binarywang/WxJava/issues/1557
    private void fixV3HttpClientConnectionPoolShutDown() {
        wxPayService.getConfig().setApiV3HttpClient(null);
    }

    static String formatDateV2(LocalDateTime time) {
        return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), PURE_DATETIME_PATTERN);
    }

    static LocalDateTime parseDateV2(String time) {
        return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN);
    }

    static LocalDateTime parseDateV2B(String time) {
        return LocalDateTimeUtil.parse(time, NORM_DATETIME_PATTERN);
    }

    static String formatDateV3(LocalDateTime time) {
        return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN);
    }

    static LocalDateTime parseDateV3(String time) {
        return LocalDateTimeUtil.parse(time, UTC_WITH_XXX_OFFSET_PATTERN);
    }

    static String getErrorCode(WxPayException e) {
        if (StrUtil.isNotEmpty(e.getErrCode())) {
            return e.getErrCode();
        }
        if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) {
            return "CUSTOM_ERROR";
        }
        return e.getReturnCode();
    }

    static String getErrorMessage(WxPayException e) {
        if (StrUtil.isNotEmpty(e.getErrCode())) {
            return e.getErrCodeDes();
        }
        if (StrUtil.isNotEmpty(e.getCustomErrorMsg())) {
            return e.getCustomErrorMsg();
        }
        return e.getReturnMsg();
    }

}
